/*
@author: stanfor
@date: 2022/9/9 14:15
@Version: 1.0
@last modify time : 2022/9/9 14:15
*/

/*
 * 身份证15位编码规则：dddddd yymmdd xx p
 * dddddd：6位地区编码
 * yymmdd: 出生年(两位年)月日，如：910215
 * xx: 顺序编码，系统产生，无法确定
 * p: 性别，奇数为男，偶数为女
 *
 * 身份证18位编码规则：dddddd yyyymmdd xxx y
 * dddddd：6位地区编码
 * yyyymmdd: 出生年(四位年)月日，如：19910215
 * xxx：顺序编码，系统产生，无法确定，奇数为男，偶数为女
 * y: 校验码，该位数值可通过前17位计算获得
 *
 * 前17位号码加权因子为 Wi = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ]
 * 验证位 Y = [ 1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2 ]
 * 如果验证码恰好是10，为了保证身份证是十八位，那么第十八位将用X来代替
 * 校验位计算公式：Y_P = mod( ∑(Ai×Wi),11 )
 * i为身份证号码1...17 位; Y_P为校验码Y所在校验码数组位置
 */
// 15位和18位身份证号码的正则表达式
export const regIdCard = /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/;
export function validateIdCardNo(idCard: any): NonNullable<boolean | undefined> {
  // 如果通过该验证，说明身份证格式正确，但准确性还需计算
  if (regIdCard.test(idCard)) {
    if (typeof idCard !== 'number' && idCard?.length === 18) {
      const idCardWi = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; // 将前17位加权因子保存在数组里
      const idCardY = [1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2]; // 这是除以11后，可能产生的11位余数、验证码，也保存成数组
      let idCardWiSum = 0; // 用来保存前17位各自乖以加权因子后的总和
      for (let i = 0; i < 17; i++) {
        idCardWiSum += idCard.substr(i, i + 1) * idCardWi[i];
      }
      const idCardMod = idCardWiSum % 11; // 计算出校验码所在数组的位置
      const idCardLast = idCard.substring(17); // 得到最后一位身份证号码
      // 如果等于2，则说明校验码是10，身份证号码最后一位应该是X
      if (idCardMod === 2) {
        return idCardLast === 'X' || idCardLast === 'x';
      }
      // 用计算出的验证码与最后一位身份证号码匹配，如果一致，说明通过，否则是无效的身份证号码
      return idCardLast === idCardY[idCardMod];
    }
  }
  return false;
}

// 校验身份证-自动带出生日和性别
export const isIdCard = (card: string) => {
  if (!card) return true;
  const num: any = card.toUpperCase();
  // 身份证号码为15位或者18位，15位时全为数字，18位前17位为数字，最后一位是校验位，可能为数字或字符X。
  if (!/^\d{17}([0-9]|X)$/.test(num)) {
    return false;
  }
  // 校验位按照ISO 7064:1983.MOD 11-2的规定生成，X可以认为是数字10。
  // 下面分别分析出生日期和校验位
  let dtmBirth: any;
  let re: any;
  let birthday: any;
  let sex: any;
  const len: any = num.length;
  if (len === 18) {
    // 获取出生日期
    birthday = `${card.substring(6, 10)}-${card.substring(10, 12,)}-${card.substring(12, 14)}`;
    // 获取性别
    sex = parseInt(card.substr(16, 1)) % 2 === 1 ? 'M' : 'F';
    re = new RegExp(/^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$/);
    const arrSplit: any = num.match(re);

    // 检查生日日期是否正确
    if (arrSplit) {
      dtmBirth = new Date(`${arrSplit[2]}/${arrSplit[3]}/${arrSplit[4]}`);
    }
    const bGoodDay = dtmBirth.getFullYear() === Number(arrSplit[2]) &&
      dtmBirth.getMonth() + 1 === Number(arrSplit[3]) &&
      dtmBirth.getDate() === Number(arrSplit[4]);
    if (!bGoodDay) {
      return false;
    }
    // 检验18位身份证的校验码是否正确。
    // 校验位按照ISO 7064:1983.MOD 11-2的规定生成，X可以认为是数字10。
    const arrInt: any = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    const arrCh: any = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
    let nTemp: any = 0;
    let i;
    for (i = 0; i < 17; i++) {
      nTemp += num.substr(i, 1) * arrInt[i];
    }
    const valnum: any = arrCh[nTemp % 11];
    if (valnum !== num.substr(17, 1)) {
      return false;
    }
  }
  return {
    birthday,
    sex,
  };
};

type validateProps = {
  isExternal: (val: any) => boolean;
  isEmail: (val: any) => boolean;
  isZipCode: (val: any) => boolean;
  isTel: (val: any) => boolean;
  isPhone: (val?: any) => boolean;
  isPhoneUser: (val: any) => boolean;
  isUSCC: (val: any) => boolean;
  isIdCardNo: (val: any) => boolean;
  isPassword: (val: any) => boolean;
  isBandcard: (val: any) => boolean;
  isCommonChar: (val: any) => boolean;
  isNormalChar: (val: any) => boolean;
  isNotZhCN: (val: any) => boolean;
  isZhCN: (val: any) => boolean;
  isBirthType: (val: any) => boolean;
  isAbbrev: (val: any) => boolean;
  isPassportType: (val: any) => boolean;
  isUSCCPolicy: (val: any) => boolean;
  isOCC: (val: any) => boolean;
  isBusinessLicense: (val: any) => boolean;
  isAlphabetNumber: (val: any) => boolean;
  isAlphabetNumberOrSpecial: (val: any) => boolean;
  isNumber: (val: any) => boolean;
  isReserveTwoPlaces: (val: any) => boolean;
  isAddress: (val: any) => boolean;
}
// 正则
export const validate: validateProps = {
  // 链接
  isExternal: (val?: any) => /^(https?:|mailto:|tel:)/.test(val),
  // 邮箱
  isEmail: (val?: any) => /^[A-Za-z0-9]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/.test(val),
  // 邮政编码
  isZipCode: (val?: any) => /^[0-9]{6,6}$/.test(val),
  // 座机
  isTel: (val?: any) => /0\d{2,3}-\d{7,8}/.test(val),
  // 手机
  isPhone: (val?: any): boolean => /^1(3|4|5|6|7|8|9)\d{9}$/.test(val),
  // 手机加区号
  isPhoneUser: (val?: any) => /^((\+?86)|(\(\+86\)|(\d{3,4}\)|\d{3,4}-|\s)))?[0-9]*$/.test(val),
  isUSCC: (val?: any) => /^[^_IOZSVa-z\W]{2}\d{6}[^_IOZSVa-z\W]{10}$/.test(val),
  // 身份证
  isIdCardNo: (val?: any) => validateIdCardNo(val),
  // 密码校验 - 英文大小写和数字，长度为 6-20 位
  isPassword: (val?: any) => /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{6,20}$/.test(val),
  // 卡片校验 - 长度 12-20 位
  isBandcard: (val?: any) => /^\d{12,20}$/g.test(val),
  // 普通字符 6-20 位
  isCommonChar: (val?: any) => /^[\w_]{6,20}$/.test(val),
  // 普通中文字符集 和 大小写英文 外加 数字
  isNormalChar: (val?: any) => /^[\u4e00-\u9fa5a-zA-Z0-9]+$/.test(val),
  // 非普通中文字符集
  isNotZhCN: (val?: any) => /^[^\u4e00-\u9fa5]+$/.test(val),
  // 普通中文字符集
  isZhCN: (val?: any) => /^[\u4e00-\u9fa5]+$/.test(val),
  // 出生类型
  isBirthType: (val?: any) => /^[a-zA-Z]\d{9}$/.test(val),
  // 缩写
  isAbbrev: (val?: any) => /^[A-Z\d]{3,6}$/.test(val),
  // 护照类型
  isPassportType: (val?: any) => val.length >= 5 && /^[^\u4e00-\u9fa5]+$/.test(val),
  isUSCCPolicy: (val?: any) => /^[A-Z0-9]{18}$/.test(val),
  isOCC: (val?: any) => /^[A-Z0-9]{8}\-[A-Z0-9]{1}$/.test(val),
  // 营业执照
  isBusinessLicense: (val?: any) => /(^\d{15}$)|(^[A-Z0-9]{18}$)/.test(val),
  // 小写字母 和 数字
  isAlphabetNumber: (val?: any) => /^[a-z0-9]+$/i.test(val),
  // 小写字母 和 数字 及 一些特殊字符
  isAlphabetNumberOrSpecial: (val?: any) =>
    /^[a-z0-9`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~！@#￥¥%……&*（）——\-+={}|《》？：“”【】、；‘'，。、]+$/i.test(
      val
    ),
  // 纯数字
  isNumber: (val?: any) => /^[0-9]*$/.test(val),
  // 校验数字和保留两位小数
  isReserveTwoPlaces: (val?: any) => /(^[1-9]\d*(\.\d{1,2})?$)|(^0(\.\d{1,2})?$)/.test(val),
  // 地址
  isAddress: (val?: any) => /村|街|路|道|弄|胡同|院|信箱|小区|号|房|室/g.test(val),
};

/** desensitizationPhone (手机号脱敏)方法说明
* @author: JasonStandFor
* @description Desc:: 将一个完整的手机号进行脱敏处理，否则返回来源手机号
* @param {string|number} mobile - 手机号
* @param {function} customizeValidate - 自定义校验方法，默认校验一般的手机号
* @return string|number
**/
// eslint-disable-next-line no-unused-vars
export const desensitizationPhone = (mobile: string|number, customizeValidate?: (mobile: string | number) => validateProps) => {
  if (customizeValidate === undefined) {
    if (!validate.isPhone(mobile)) {
      console.warn('unexpected: 传入的手机号异常');
      return mobile;
    }
    return `${mobile}`.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2');
  }
  if (!customizeValidate(mobile)) {
    console.warn('unexpected: 传入的手机号异常');
    return mobile;
  }
  // 中间组进行 脱敏
  return `${mobile}`.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
};

type desensitizationParams = {
  // 开始脱敏的位置
  startCount: number;
  // 结束脱敏的位置
  endCount: number;
  // 脱敏的文字样式
  charSet: string;
  // 需要替换的文本
  ruleDefinition: Array<string>;
}

/** desensitizationText (文字脱敏)方法说明
* @author: JasonStandFor
* @description Desc:: 两种方式处理需要进行脱敏的文本：1、输入起始脱敏位置下标和结束脱敏位置下标 2、输入脱敏文本的数组
* @param {string} text - 需要脱敏的文本
* @param {object} other - 需要脱敏的一些属性
* @return string
* @example
例1：
desensitizationText('15374238927', { startCount: 3, endCount: 4 });
===> 1537****927;
例2：
desensitizationText('据统计，北京现有胡同1000多条，纵横交错，织成了荟萃万千的老北京景观', { ruleDefinition: ['纵横交错', '，'] })
===> 据统计*北京现有胡同1000多条******织成了荟萃万千的老北京景观;
**/
export const desensitizationText = (text: string, other: Partial<desensitizationParams> = { startCount: 0 }): string => {
  let str = text;
  const calcDesensitizationStr = (strCount: number = other.endCount || 1): string => {
    let desensitizationStr = other.charSet || '*';
    let i = 1;
    while (i < strCount) {
      desensitizationStr += other.charSet || '*';
      i++;
    }
    return desensitizationStr;
  };
  if (Array.isArray(other.ruleDefinition)) {
    for (let i = 0, len = other.ruleDefinition.length; i < len; ++i) {
      str = str.replaceAll(other.ruleDefinition[i], calcDesensitizationStr(other.ruleDefinition[i].length));
    }
    return str;
  }
  return `${text}`.replace(str.substr(other.startCount || 0, other.endCount || 1), calcDesensitizationStr());
};

